package com.xlh.service.courseTest.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONArray;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.xlh.common.ResultEnum;
import com.xlh.dao.courseTest.PracticeMapper;
import com.xlh.dao.courseTest.PracticeMapperExt;
import com.xlh.dao.courseTest.helper.CourseQuestionRepositoryDetailsMapperHelper;
import com.xlh.dao.courseTest.helper.CourseQuestionRepositoryMapperHelper;
import com.xlh.dao.user.helper.UserClassMapperHelper;
import com.xlh.dto.courseTest.AnswerSheetDTO;
import com.xlh.dto.courseTest.AnswerSheetTypeDTO;
import com.xlh.dto.courseTest.ReadingDTO;
import com.xlh.dto.courseTest.TypeReadingDTO;
import com.xlh.dto.courseTest.check.ChapterCountDTO;
import com.xlh.dto.courseTest.check.ClassUncheckedCountDTO;
import com.xlh.dto.courseTest.check.StudentBaseListDTO;
import com.xlh.dto.courseTest.check.StudentListDTO;
import com.xlh.dto.courseTest.practice.*;
import com.xlh.dto.user.UserInfoDTO;
import com.xlh.enums.FileTypeEnum;
import com.xlh.enums.LocalResourcesEnum;
import com.xlh.enums.PermissionEnum;
import com.xlh.enums.check.SubmitStatusEnum;
import com.xlh.enums.courseTest.PaperStatusEnum;
import com.xlh.enums.courseTest.QuestionBusinessTypeEnum;
import com.xlh.enums.courseTest.QuestionTypeEnum;
import com.xlh.exception.common.GlobalException;
import com.xlh.param.check.CheckSubmitParam;
import com.xlh.param.courseTest.PracticeGetParam;
import com.xlh.pojo.courseTest.CourseQuestionRepository;
import com.xlh.pojo.courseTest.CourseQuestionRepositoryDetails;
import com.xlh.pojo.courseTest.Practice;
import com.xlh.pojo.courseTest.PracticeExample;
import com.xlh.pojo.system.Permission;
import com.xlh.pojo.user.UserClass;
import com.xlh.security.UserInfoHolder;
import com.xlh.service.courseTest.PracticeService;
import com.xlh.service.courseTest.QuestionRepositoryDetailsService;
import com.xlh.service.courseTest.QuestionRepositoryService;
import com.xlh.service.courseTest.UserClassService;
import com.xlh.service.file.FileService;
import com.xlh.service.system.SystemConfigService;
import com.xlh.service.user.UserService;
import com.xlh.util.BeanUtil;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author zhr 2019/4/1
 */
@Service
public class PracticeServiceImpl implements PracticeService {

    private static Logger logger = LoggerFactory.getLogger(PracticeServiceImpl.class);

    @Autowired
    private QuestionRepositoryService questionService;
    @Autowired
    private QuestionRepositoryDetailsService detailsService;
    @Autowired
    private QuestionRepositoryService repositoryService;
    @Autowired
    private UserInfoHolder userInfoHolder;
    @Autowired
    private FileService fileService;
    @Autowired
    private UserClassService userClassService;
    @Autowired
    private UserClassMapperHelper userClassMapperHelper;
    @Autowired
    private PracticeMapper mapper;
    @Autowired
    private PracticeMapperExt mapperExt;
    @Autowired
    private UserService userService;
    @Autowired
    private CourseQuestionRepositoryMapperHelper courseQuestionRepositoryMapperHelper;
    @Autowired
    private CourseQuestionRepositoryDetailsMapperHelper courseQuestionRepositoryDetailsMapperHelper;

    @Autowired
    private SystemConfigService configService;

    /**
     * 记录随堂练习的缓存
     */
    private static Cache<PracticeGetParam, List<AnswerSheetDTO>> practiceCache = CacheBuilder.newBuilder()
            .initialCapacity(100)
            .expireAfterAccess(30, TimeUnit.SECONDS)
            .removalListener(removal -> logger.info(removal.getKey() + " was removed, cause is " + removal.getCause()))
            .build();


    @Override
    public void putCache(PracticeGetParam param, List<AnswerSheetDTO> answerSheetInfo) {
        practiceCache.put(param, answerSheetInfo);
    }

    @Override
    public List<AnswerSheetDTO> getCache(PracticeGetParam param) {
        // 从缓存集中读取数据，如果缓存集中没有数据，则直接从题库中取数据并进行缓存
        List<AnswerSheetDTO> answerSheetList = practiceCache.getIfPresent(param);

        if (CollectionUtils.isEmpty(answerSheetList)) {
            answerSheetList = Lists.newArrayList();
//            if (isSubmitted(param.getChapterId(), param.getUserId())) {
//                Practice practice = selectByChapterIdStudentId(param.getChapterId(), param.getUserId()).get(0);
//                answerSheetList.addAll(AnswerSheetDTO.parse(practice.getContent()));
//            } else {
            List<CourseQuestionRepository> questionList = questionService.selectQuestionIdByChapterId(param.getChapterId(), QuestionBusinessTypeEnum.PRACTICE);
            if (CollectionUtils.isEmpty(questionList)) {
                throw new GlobalException("随堂练习不存在或已删除");
            }
            for (CourseQuestionRepository question : questionList) {
                int orderNumber = questionList.indexOf(question) + 1;
                answerSheetList.add(new AnswerSheetDTO(question.getId(), orderNumber, question.getQuestionType()));
            }
//            }

            putCache(param, answerSheetList);
        }

        return answerSheetList;
    }

    @Override
    public void removeCache(PracticeGetParam param) {
        practiceCache.invalidate(param);
    }

    @Override
    public PracticeDTO getPractice(Long chapterId, Long questionId, Long userId, Boolean isTeacher, Long courseId) {
        return isTeacher ? teacherPractice(chapterId, questionId) : studentPractice(chapterId, questionId, userId, courseId);

    }

    @Override
    public PracticeDTO getPracticeOther(Long chapterId, Long questionId, List<Long> questionIds, Boolean isTeacher, Long courseId) {
        return teacherPracticeOther(chapterId, questionId, questionIds);

    }

    private PracticeDTO studentPractice(Long chapterId, Long questionId, Long userId, Long courseId) {
        return isSubmitted(chapterId, userId, courseId) ? getSubmit(chapterId, userId, courseId) : getNotSubmit(chapterId, questionId, userId);
    }

    private PracticeDTO teacherPractice(Long chapterId, Long questionId) {
        NotSubmitDTO result = new NotSubmitDTO();

        List<CourseQuestionRepository> questionList = questionService.selectQuestionIdByChapterId(chapterId, QuestionBusinessTypeEnum.PRACTICE);
        if (CollectionUtils.isEmpty(questionList)) {
            throw new GlobalException("随堂练习不存在或已删除");
        }

        result.setAnswerSheetList(transformRepository(questionList));

        // questionId为空时，查询缓存table中的答题卡列表，并取列表第一个对象的id赋给questionId
        int orderNumber;
        if (questionId == null) {
            questionId = questionList.get(0).getId();
        }

        orderNumber = questionList.stream().map(CourseQuestionRepository::getId).collect(Collectors.toList()).indexOf(questionId) + 1;

        // 查询题目，如果题目为选择题，则继续查询选项
        CourseQuestionRepository question = questionService.selectByPrimaryKey(questionId);
        if (question == null) throw new GlobalException("随堂练习不存在或已删除");

        // 选择题设置选项和是否多选题
        if (Objects.equals(question.getQuestionType(), QuestionTypeEnum.CHOICE.getCode())) {
            List<CourseQuestionRepositoryDetails> choiceList =
                    detailsService.getDetailsList(Lists.newArrayList(questionId), FileTypeEnum.HTML);
            result.convert(question, orderNumber, null, questionList.size(), choiceList);
        } else {
            result.convert(question, orderNumber, null, questionList.size(), null);
        }

        List<CourseQuestionRepositoryDetails> answerList = detailsService.getDetailsList(Lists.newArrayList(questionId), FileTypeEnum.HTML);
        QuestionTypeEnum typeEnum = QuestionTypeEnum.forCode(question.getQuestionType());
        if (typeEnum != null) {
            result.setRightAnswer(typeEnum.getRightPracticeAnswer(answerList));
        }
        return result;
    }

    private PracticeDTO teacherPracticeOther(Long chapterId, Long questionId, List<Long> questionIds) {
        NotSubmitDTO result = new NotSubmitDTO();

        List<CourseQuestionRepository> questionList1 = questionService.selectQuestionIdByChapterId(chapterId, QuestionBusinessTypeEnum.PRACTICE);
        List<CourseQuestionRepository> questionList = questionList1.stream().filter(CourseQuestionRepository -> questionIds.contains(CourseQuestionRepository.getId())).collect(Collectors.toList());

        if (CollectionUtils.isEmpty(questionList)) {
            throw new GlobalException("随堂练习不存在或已删除");
        }

        result.setAnswerSheetList(transformRepository(questionList));

        // questionId为空时，查询缓存table中的答题卡列表，并取列表第一个对象的id赋给questionId
        int orderNumber;
        if (questionId == null) {
            questionId = questionList.get(0).getId();
        }

        orderNumber = questionList.stream().map(CourseQuestionRepository::getId).collect(Collectors.toList()).indexOf(questionId) + 1;

        // 查询题目，如果题目为选择题，则继续查询选项
        CourseQuestionRepository question = questionService.selectByPrimaryKey(questionId);
        if (question == null) throw new GlobalException("随堂练习不存在或已删除");

        // 选择题设置选项和是否多选题
        if (Objects.equals(question.getQuestionType(), QuestionTypeEnum.CHOICE.getCode())) {
            List<CourseQuestionRepositoryDetails> choiceList =
                    detailsService.getDetailsList(Lists.newArrayList(questionId), FileTypeEnum.HTML);
            result.convert(question, orderNumber, null, questionList.size(), choiceList);
        } else {
            result.convert(question, orderNumber, null, questionList.size(), null);
        }

        List<CourseQuestionRepositoryDetails> answerList = detailsService.getDetailsList(Lists.newArrayList(questionId), FileTypeEnum.HTML);
        QuestionTypeEnum typeEnum = QuestionTypeEnum.forCode(question.getQuestionType());
        if (typeEnum != null) {
            result.setRightAnswer(typeEnum.getRightPracticeAnswer(answerList));
        }
        return result;
    }

    private List<AnswerSheetTypeDTO> transformRepository(List<CourseQuestionRepository> questionList) {
        List<AnswerSheetDTO> answerSheetList = Lists.newArrayList();
        for (CourseQuestionRepository question : questionList) {
            int orderNumber = questionList.indexOf(question) + 1;
            answerSheetList.add(new AnswerSheetDTO(question.getId(), orderNumber, question.getQuestionType()));
        }
        return AnswerSheetTypeDTO.transform(answerSheetList);
    }

    private NotSubmitDTO getNotSubmit(Long chapterId, Long questionId, Long userId) {
        NotSubmitDTO result = new NotSubmitDTO();

        PracticeGetParam param = new PracticeGetParam(userId, chapterId);

        // 从缓存中取数据
        List<AnswerSheetDTO> answerSheetList = getCache(param);

        Map<Long, AnswerSheetDTO> idAnswerSheetListMap = BeanUtil.mapByKey("id", answerSheetList);

        // questionId为空时，查询缓存table中的答题卡列表，并取列表第一个对象的id赋给questionId
        if (questionId == null) {
            questionId = answerSheetList.get(0).getId();
        }

        // 查询题目，如果题目为选择题，则继续查询选项
        CourseQuestionRepository question = questionService.selectByPrimaryKey(questionId);
        if (question == null) throw new GlobalException("随堂练习不存在或已删除");

        AnswerSheetDTO answerDTO = idAnswerSheetListMap.get(questionId);

        // 选择题设置选项和是否多选题
        if (Objects.equals(question.getQuestionType(), QuestionTypeEnum.CHOICE.getCode())) {
            List<CourseQuestionRepositoryDetails> choiceList =
                    detailsService.getDetailsList(Lists.newArrayList(questionId), FileTypeEnum.HTML);
            result.convert(question, answerDTO.getOrderNumber(), answerDTO.getAnswer(), answerSheetList.size(), choiceList);
        } else {
            result.convert(question, answerDTO.getOrderNumber(), answerDTO.getAnswer(), answerSheetList.size(), null);
        }

        result.setAnswerSheetList(AnswerSheetTypeDTO.transform(answerSheetList));
        return result;
    }

    private SubmitDTO getSubmit(Long chapterId, Long userId, Long courseId) {
        // 使用userId和chapterId把当前的userId转为user_class关联id
        Long userClassId = userClassService.getUserClassId(courseId, userId);
        if (userClassId == null) {
            throw new GlobalException("异常的用户-课程关系");
        }
        // 是否展示答案
        boolean showAnswer = showAnswer(chapterId);

        Practice practice = selectByChapterIdStudentId(chapterId, userClassId).get(0);
        List<TypeReadingDTO> readingList = getDetails(practice);

        // 如果不显示答案，需要处理数据
        if (!showAnswer && CollectionUtils.isNotEmpty(readingList)) {
            readingList.forEach(data -> {
                data.setWrong(0);
                data.setRight(0);
                if (CollectionUtils.isNotEmpty(data.getQuestionList())) {
                    data.getQuestionList().forEach(questionData -> {
                        questionData.setRight(true);
                        questionData.setRightAnswer(null);
                        questionData.setJxContent(null);
                        questionData.setJxContentHtml(null);
                    });
                }
            });
        }
        int score = readingList.stream().map(TypeReadingDTO::getTotalScore).reduce(0, Integer::sum);
        return new SubmitDTO(readingList, score, practice.getCheckResult(), showAnswer);
    }

    @Override
    public void submitPractice(Long chapterId, List<AnswerSheetTypeDTO> answerSheetTypeList, Long userId, Long courseId) {
        // 使用userId和chapterId把当前的userId转为user_class关联id
        Long userClassId = userClassService.getUserClassId(courseId, userId);

        if (userClassId == null) {
            throw new GlobalException("未关联班级，暂时不可提交答案");
        }

        if (isSubmitted(chapterId, userId, courseId)) {
            throw new GlobalException("已提交随堂练习，不要重复提交");
        }


        List<AnswerSheetDTO> answerSheetList = AnswerSheetDTO.parse(answerSheetTypeList);
        AnswerSheetDTO.parseOther(answerSheetTypeList);
        // 过滤出客观题，并查询答案
        List<Long> questionIds = answerSheetList.stream()
                .filter(dto -> QuestionTypeEnum.isObjective(dto.getType()))
                .map(AnswerSheetDTO::getId).collect(Collectors.toList());

        // 如果有客观题,就进行客观题的批阅
        if (CollectionUtils.isNotEmpty(questionIds)) {
            List<CourseQuestionRepositoryDetails> answerList = detailsService.getDetailsList(questionIds, FileTypeEnum.HTML);
            if (CollectionUtils.isEmpty(answerList)) {
                throw new GlobalException("题目不存在或已删除,请联系授课教师");
            }

            Map<Long, List<CourseQuestionRepositoryDetails>> questionIdAnswerList = BeanUtil.aggByKeyToList("questionId", answerList);

            // 批阅客观题
            for (AnswerSheetDTO dto : answerSheetList) {
                if (questionIdAnswerList.get(dto.getId()) != null) {
                    List<CourseQuestionRepositoryDetails> questionAnswerList = questionIdAnswerList.get(dto.getId());

                    try {
                        QuestionTypeEnum typeEnum = QuestionTypeEnum.forCode(dto.getType());
                        if (typeEnum != null) typeEnum.checkPractice(dto, questionAnswerList);
                    } catch (UnsupportedOperationException e) {
                        logger.info("题型不支持自动批阅,已跳过");
                    } catch (Exception e) {
                        logger.error("自动批阅异常", e);
                        throw new GlobalException("自动批阅异常");
                    }
                }
            }
        }

        // 移除缓存中的内容
        removeCache(new PracticeGetParam(userId, chapterId));

        // 存数据
        insertSelective(chapterId, userClassId, AnswerSheetTypeDTO.transform(answerSheetList));
    }

    @Override
    public void insertSelective(Long chapterId, Long userClassId, List<AnswerSheetTypeDTO> answerSheetTypeList) {
        Practice practice = new Practice();
        practice.setChapterId(chapterId);
        practice.setStudentId(userClassId);
        practice.setContent(JSONArray.toJSONString(answerSheetTypeList));
        mapper.insertSelective(practice);
    }

    @Override
    public List<ChapterCountDTO> countGroupByChapterIdClassId(List<Long> chapterIds) {
        List<ChapterCountDTO> list = mapperExt.countGroupByChapterId(chapterIds);
        return CollectionUtils.isEmpty(list) ? Lists.newArrayList() : list;
    }

    @Override
    public Practice selectByPrimaryKey(Long id) {
        return mapper.selectByPrimaryKey(id);
    }

    @Override
    public void deletePractice(List<Long> chapterIds) {
        PracticeExample example = new PracticeExample();
        example.createCriteria().andChapterIdIn(chapterIds);
        mapper.deleteByExample(example);
    }

    @Override
    public void deletePractice(List<Long> userClassIds, List<Long> chapterIds) {
        // todo 需要修改

        PracticeExample example = new PracticeExample();
        PracticeExample.Criteria criteria = example.createCriteria();
        criteria.andStudentIdIn(userClassIds);
        if (CollectionUtils.isEmpty(chapterIds)) {
            criteria.andChapterIdIn(chapterIds);
        }
        mapper.deleteByExample(example);
    }

    @Override
    public List<Practice> selectByChapterIdStudentIds(Long chapterId, List<Long> userClassIds) {
        PracticeExample example = new PracticeExample();
        if (CollectionUtils.isEmpty(userClassIds)) return Lists.newArrayList();
        example.createCriteria().andChapterIdEqualTo(chapterId).andStudentIdIn(userClassIds).andDelMarkEqualTo(false);
        return mapper.selectByExampleWithBLOBs(example);
    }

    /**
     * 是否已经提交过随堂练习
     */
    @Override
    public Boolean isSubmitted(Long chapterId, Long userId, Long courseId) {
        // 使用userId和chapterId把当前的userId转为user_class关联id
        Long userClassId = userClassService.getUserClassId(courseId, userId);
        if (userClassId == null) {
            return false;
        }

        return isSubmitted(chapterId, userClassId);
    }

    /**
     * 是否已经提交过随堂练习
     *
     * @param chapterId   章节id
     * @param userClassId 用户-班级关联id，相当于userId
     * @return
     */
    private Boolean isSubmitted(Long chapterId, Long userClassId) {
        return CollectionUtils.isNotEmpty(selectByChapterIdStudentId(chapterId, userClassId));
    }

    @Override
    public List<TypeReadingDTO> getDetails(Practice practice) {
        // 反序列化答案字符串
        List<AnswerSheetTypeDTO> answerTypeList = JSONArray.parseArray(practice.getContent(), AnswerSheetTypeDTO.class);
        Map<Integer, Integer> typeScoreMap = answerTypeList.stream().collect(Collectors.toMap(
                AnswerSheetTypeDTO::getType, AnswerSheetTypeDTO::getScore
        ));
        List<AnswerSheetDTO> answerList = AnswerSheetDTO.parse(answerTypeList);
        Map<Long, AnswerSheetDTO> questionIdUserAnswerMap = BeanUtil.mapByKey("id", answerList);
        // 查询题干和参考答案、选项信息
        List<Long> questionIds = answerList.stream().map(AnswerSheetDTO::getId).collect(Collectors.toList());
        List<CourseQuestionRepository> questionList = repositoryService.selectQuestionByIds(questionIds);
        Map<Long, CourseQuestionRepository> idQuestionListMap = BeanUtil.mapByKey("id", questionList);
        // 提取出题干中的markdown文本并转为html字符串
        questionList.forEach(repository -> repository.setContent(repository.getHtmlContent()));
        Map<Long, List<CourseQuestionRepositoryDetails>> questionIdAnswerListMap = detailsService.getDetailsMap(questionIds, FileTypeEnum.HTML);

        // 解析答题卡详情数据
        List<ReadingDTO> readingList = BeanUtil.batchTransform(ReadingDTO.class, questionList);
        readingList.forEach(reading -> reading.convert(questionIdUserAnswerMap, questionIdAnswerListMap, idQuestionListMap.get(reading.getId())));

        // 将readingList转为typeReadingList
        List<TypeReadingDTO> typeReadingList = TypeReadingDTO.transform(readingList);
        typeReadingList.forEach(typeReadingDTO -> typeReadingDTO.setTotalScore(typeScoreMap.get(typeReadingDTO.getType())));

        return typeReadingList;
    }

    @Override
    public void updateByPrimaryKey(Practice practice) {
        mapper.updateByPrimaryKeyWithBLOBs(practice);
    }

    @Override
    public List<StudentListDTO> listStudent(Long classId, Long chapterId, Integer status, String studentInfo, Long userId) {
        List<StudentListDTO> list = mapperExt.listStudent(classId, chapterId, status, studentInfo);
        if (CollectionUtils.isNotEmpty(list)) {
            list.forEach(dto -> dto.setSubmit(dto.getId() != null));
        }
        return list;
    }

    @Override
    public Long getCount(Long userId) {
        List<UserClass> userClassList = userClassService.getUserClassIds(null, userId);
        List<Long> userClassIds = userClassList.stream().map(UserClass::getId).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(userClassIds)) {
            return 0L;
        }

        PracticeExample example = new PracticeExample();
        example.createCriteria().andStudentIdIn(userClassIds).andDelMarkEqualTo(false);
        return mapper.countByExample(example);
    }

    @Override
    public Map<Long, PaperStatusEnum> getChapterPracticeMap(List<Long> chapterIds, Long courseId) {
        Map<Long, PaperStatusEnum> result = Maps.newHashMap();
        Long userId = userInfoHolder.getUserId();

        // 使用userId和chapterId把当前的userId转为user_class关联id
        Long userClassId = userClassService.getUserClassId(courseId, userId);
        if (userClassId == null) {
            return result;
        }

        PracticeExample example = new PracticeExample();
        example.createCriteria().andStudentIdEqualTo(userClassId).andChapterIdIn(chapterIds).andDelMarkEqualTo(false);
        List<Practice> practiceList = mapper.selectByExample(example);
        Map<Long, Practice> chapterIdPracticeMap = BeanUtil.mapByKey("chapterId", practiceList);
        for (Long chapterId : chapterIds) {
            if (chapterIdPracticeMap.get(chapterId) == null) {
                result.put(chapterId, PaperStatusEnum.NOT_TEST);
            } else {
                Practice practice = chapterIdPracticeMap.get(chapterId);
                result.put(chapterId, practice.getCheck() ? PaperStatusEnum.READ : PaperStatusEnum.READING);
            }
        }

        return result;
    }


    @Override
    public List<StudentBaseListDTO> listStudentBase(Long courseId, Long userId) {
        List<StudentBaseListDTO> list = mapperExt.listStudentBase(courseId, userId);
        return CollectionUtils.isEmpty(list) ? Lists.newArrayList() : list;
    }

    @Override
    public List<StudentListDTO> listStudentBaseOther(Long userId, Long courseId, Long chapterId, Long classId) {
        List<StudentListDTO> practice = mapperExt.listStudentBaseOther(userId, courseId, chapterId, classId);

        for (int i = 0; i < practice.size(); i++) {
            StudentListDTO studentListDTO = practice.get(i);
            studentListDTO.setRank(i + 1);
        }
        return practice;
    }

    @Async
    @Override
    public void updatePDF(String fileName, CheckSubmitParam param) {
        String filePath = LocalResourcesEnum.PDF.getLocalUploadPath(fileName);

        fileService.html2pdf(filePath, param.getHtmlStr());
    }

    @Override
    public List<Practice> selectByChapterIdsClassId(List<Long> chapterIds, Long classId) {
        List<Practice> list = mapperExt.selectByChapterIdsClassId(chapterIds, classId);
        return CollectionUtils.isEmpty(list) ? Lists.newArrayList() : list;
    }

    @Override
    public List<Practice> selectByChapterIdsWithoutBlobs(List<Long> chapterIds) {
        PracticeExample example = new PracticeExample();
        example.createCriteria().andChapterIdIn(chapterIds).andDelMarkEqualTo(false);
        return mapper.selectByExample(example);
    }

    @Override
    public List<ClassUncheckedCountDTO> selectUncheckedByCourseIds(List<Long> courseIdList) {
        List<ClassUncheckedCountDTO> list = mapperExt.selectUncheckedByCourseIds(courseIdList);
        return CollectionUtils.isEmpty(list) ? Lists.newArrayList() : list;
    }

    @Override
    public void savePractice(Long chapterId, List<AnswerSheetTypeDTO> answerSheetTypeList, Long userId, Long courseId) {
        // 使用userId和chapterId把当前的userId转为user_class关联id
        Long userClassId = userClassService.getUserClassId(courseId, userId);
        if (userClassId == null) {
            return;
        }

        List<AnswerSheetDTO> answerSheetList = AnswerSheetDTO.parse(answerSheetTypeList);

        putCache(new PracticeGetParam(userId, chapterId), answerSheetList);
    }

    @Override
    public PracticeChapterDTO chapterStatistics(Long chapterId, Long classId) {

        if(ObjectUtil.isNull(chapterId) || ObjectUtil.isNull(classId)){
            throw new GlobalException(ResultEnum.BAD_REQUEST);
        }
        // 1. 查询当前课程下的所有学生
        List<UserInfoDTO> userInfoDTOS = userService.queryClassUser(Lists.newArrayList(classId), null);
        List<UserClass> userClasses = userClassMapperHelper.findByCondition(classId);
        List<Long> studentIds = userClasses.stream().map(UserClass::getId).collect(Collectors.toList());

        List<PracticeStudentInfoDTO> allUsers = convertUserInfoToPracticeStudent(userInfoDTOS);
        // 1.2 查询当下学生的平均分数
        String average = mapperExt.getGradeAvg(chapterId, studentIds);
        List<PracticeStudentInfoDTO> committedUsers = Lists.newArrayList();
        if(CollectionUtil.isNotEmpty(userInfoDTOS)){
            committedUsers = mapperExt.practiceStudentInfo(chapterId, userInfoDTOS);
        }
        List<PracticeStudentInfoDTO> uncommittedUsers = filterCommittedUsers(allUsers, committedUsers);
        // 4. 组装对象返回
        PracticeChapterDTO practiceChapterDTO = new PracticeChapterDTO();
        practiceChapterDTO.setCommittedUsers(committedUsers);
        practiceChapterDTO.setUncommittedUsers(uncommittedUsers);
        practiceChapterDTO.setAverage(NumberUtil.round(average, 2).toString());
        practiceChapterDTO.setCountUser(userInfoDTOS.size());

        return practiceChapterDTO;
    }

    /**
     * 获取未参与的用户
     *
     * @param allUsers
     * @param committedUsers
     * @return
     */
    private List<PracticeStudentInfoDTO> filterCommittedUsers(List<PracticeStudentInfoDTO> allUsers,
                                                              List<PracticeStudentInfoDTO> committedUsers) {

        List<PracticeStudentInfoDTO> unCommittedUser = Lists.newArrayList();
        outer:
        for (PracticeStudentInfoDTO allUser : allUsers) {
            for (PracticeStudentInfoDTO committedUser : committedUsers) {
                if (allUser.getId().equals(committedUser.getId())) {
                    continue outer;
                }
            }
            unCommittedUser.add(allUser);
        }

        return unCommittedUser;
    }

    /**
     * 转换用户对象为prictice学生 初始化对象分数为0
     *
     * @param userInfoDTOS
     * @return
     */
    private List<PracticeStudentInfoDTO> convertUserInfoToPracticeStudent(List<UserInfoDTO> userInfoDTOS) {

        List<PracticeStudentInfoDTO> lists = Lists.newArrayList();
        for (UserInfoDTO userInfoDTO : userInfoDTOS) {
            PracticeStudentInfoDTO practiceStudentInfoDTO = new PracticeStudentInfoDTO();
            practiceStudentInfoDTO.setId(userInfoDTO.getId());
            practiceStudentInfoDTO.setStudentName(userInfoDTO.getName());
            practiceStudentInfoDTO.setScore("0");
            lists.add(practiceStudentInfoDTO);
        }
        return lists;
    }

    /**
     * 通过章节id和学生id查询提交的随堂练习
     */
    private List<Practice> selectByChapterIdStudentId(Long chapterId, Long userClassId) {
        PracticeExample example = new PracticeExample();
        example.createCriteria().andChapterIdEqualTo(chapterId).andStudentIdEqualTo(userClassId).andDelMarkEqualTo(false);
        return mapper.selectByExampleWithBLOBs(example);
    }


    @Override
    public List<PrictceCourseStatisticsDTO> findPracticeByChapterIdsAndUserIds(List<Long> chapterIds, List<Long> userIds) {

        List<PrictceCourseStatisticsDTO> userPracticeInfo = mapperExt.findPracticeByChapterIdsAndUserIds(chapterIds, userIds);
        Set<PrictceCourseStatisticsDTO> allPractice = new HashSet<>();
        loadObjectiveNumber(userPracticeInfo);
        allPractice.addAll(new HashSet(userPracticeInfo));
        for (Long userId : userIds) {
            for (Long chapterId : chapterIds) {
                PrictceCourseStatisticsDTO practice = new PrictceCourseStatisticsDTO();
                practice.setUserId(userId).setChapterId(chapterId);
                practice.setSubmitStatus(SubmitStatusEnum.NO.getSubmit());
                allPractice.add(practice);
            }
        }

        return new ArrayList<PrictceCourseStatisticsDTO>(allPractice);
    }


    /**
     * 随堂练习正确率统计
     *
     * @param chapterId
     * @param classId   班级ID
     * @return
     */
    @Override
    public PracticeAccuracyTypeWrapDTO accuracyStatistics(Long chapterId, Long classId) {

        List<PracticeAccuracyTypeDTO> result = Lists.newArrayList();

        List<PracticeAccuracyDTO> practiceAccuracyDTOs = Lists.newArrayList();

        List<UserClass> userClasses = userClassMapperHelper.findByCondition(classId);

        List<Long> userClassIds = userClasses.stream().map(x -> x.getId()).collect(Collectors.toList());

        // 1. 根据章节ID查询
        List<CourseQuestionRepository> questions = courseQuestionRepositoryMapperHelper.findQuestionByChapterId(chapterId);

        List<Long> questionIds = questions.stream().map(CourseQuestionRepository::getId).collect(Collectors.toList());
        // 2. 根据题号查询题目详细情况
        List<CourseQuestionRepositoryDetails> courseQuestionRepositoryDetails = courseQuestionRepositoryDetailsMapperHelper.findQestionDetails(questionIds);
        Map<Long, List<CourseQuestionRepositoryDetails>> questionMap = courseQuestionRepositoryDetails.stream().collect(Collectors.groupingBy(CourseQuestionRepositoryDetails::getQuestionId));

        // 3. 获取所有学生的答题情况
        List<Practice> practices = mapperExt.getRecentPractice(chapterId, userClassIds);

        // 4. 拆解获取正确答案
        List<AnswerSheetDTO> allAnswer = Lists.newArrayList();
        for (Practice practice : practices) {
            List<AnswerSheetTypeDTO> answerTypeList = JSONArray.parseArray(practice.getContent(), AnswerSheetTypeDTO.class);
            for (AnswerSheetTypeDTO answerSheetTypeDTO : answerTypeList) {
                allAnswer.addAll(answerSheetTypeDTO.getAnswerSheetList());
            }
        }
        Map<Long, List<AnswerSheetDTO>> mapQuestionKey = allAnswer.stream().collect(Collectors.groupingBy(AnswerSheetDTO::getId));
        // 5. 根据题号统计最终的答题情况
        for (CourseQuestionRepository questionRepository : questions) {

            List<AnswerSheetDTO> answerSheetDTOS = mapQuestionKey.get(questionRepository.getId());
            if (ObjectUtil.isNull(answerSheetDTOS)) answerSheetDTOS = Lists.newArrayList();
            PracticeAccuracyDTO practiceAccuracyDTO = BeanUtil.transform(PracticeAccuracyDTO.class, questionRepository);
            practiceAccuracyDTO.setContent(questionRepository.getHtmlContent());
            // 回答问题总数
            Long answerCount = Long.valueOf(practices.size());
            // 所有学生总数
            Long allStudent = Long.valueOf(userClasses.size());
            // 回答正确人数
            Long correctCount = Long.valueOf(answerSheetDTOS.stream().filter(x -> x.getRight() == true).count());
            // 回答问题的正确率
            double accuracy = 0d;
            if (correctCount != 0)
                accuracy = NumberUtil.mul(NumberUtil.div(Double.valueOf(correctCount), Double.valueOf(answerCount), 2), 100);
            ;
            // 装载参数
            practiceAccuracyDTO.setAnswerCount(answerCount).setAllStudent(allStudent).setCorrectCount(correctCount).setAccuracy(accuracy);
            List<CourseQuestionRepositoryDetails> questionDetails = questionMap.get(questionRepository.getId());
            loadProperty(practiceAccuracyDTO, questionDetails);
            practiceAccuracyDTOs.add(practiceAccuracyDTO);
        }
        // 进行排序的封装
        Map<Integer, List<PracticeAccuracyDTO>> questionType = practiceAccuracyDTOs.stream().collect(Collectors.groupingBy(PracticeAccuracyDTO::getQuestionType));
        // 对key键进行排序
        LinkedHashMap<Integer, List<PracticeAccuracyDTO>> linkMap = questionType.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                        (oldValue, newValue) -> oldValue, LinkedHashMap::new));

        for (Integer integer : linkMap.keySet()) {

            PracticeAccuracyTypeDTO typeDTO = new PracticeAccuracyTypeDTO();
            typeDTO.setType(integer);
            QuestionTypeEnum questionTypeEnum = QuestionTypeEnum.forCode(integer);
            if (questionTypeEnum != null) {
                typeDTO.setTypeName(questionTypeEnum.getMessage());
            }
            typeDTO.setQuestionList(questionType.get(integer));
            result.add(typeDTO);
        }


        return new PracticeAccuracyTypeWrapDTO(result);
    }

    /**
     * 装载正确答案等参数
     *
     * @param practiceAccuracyDTO
     * @param questionDetails
     */
    private void loadProperty(PracticeAccuracyDTO practiceAccuracyDTO, List<CourseQuestionRepositoryDetails> questionDetails) {

        QuestionTypeEnum typeEnum = QuestionTypeEnum.forCode(practiceAccuracyDTO.getQuestionType());

        if (typeEnum != null) {
            switch (typeEnum) {
                case CHOICE:
                    List<Integer> rightAnswerList = Lists.newArrayList();
                    practiceAccuracyDTO.setChoiceList(questionDetails.stream().map(CourseQuestionRepositoryDetails::getHtmlContent).collect(Collectors.toList()));
                    for (CourseQuestionRepositoryDetails choice : questionDetails) {
                        if (choice.getIsCorrect()) rightAnswerList.add(choice.getOrderNumber());
                    }
                    practiceAccuracyDTO.setIsMultipleChoice(rightAnswerList.size() > 1);
                    practiceAccuracyDTO.setRightAnswer(rightAnswerList.toArray());
                    break;
                case JUDGEMENT:
                    practiceAccuracyDTO.setRightAnswer(questionDetails.get(0).getIsCorrect());
                    break;
                case BLANK:
                case SAQ:
                    practiceAccuracyDTO.setRightAnswer(questionDetails.get(0).getHtmlContent());
                    break;
                case EXPERIMENT:
                    practiceAccuracyDTO.setRightAnswer(questionDetails.get(0).getContent());
                    break;
                default:
                    break;
            }
        }

    }

    /**
     * 装载客观题数目
     *
     * @param userPracticeInfo
     */
    private void loadObjectiveNumber(List<PrictceCourseStatisticsDTO> userPracticeInfo) {
        for (PrictceCourseStatisticsDTO practiceRecord : userPracticeInfo) {
            List<TypeReadingDTO> details = getDetails(practiceRecord);
            Integer objectiveItemCount = 0;
            Integer objectiveItemErrorCount = 0;
            for (TypeReadingDTO detail : details) {
                boolean isObjective = QuestionTypeEnum.isObjective(detail.getType());
                if (isObjective) {
                    Long wrongQuestion = detail.getQuestionList().stream().map(ReadingDTO::getRight).filter(x -> x == false).count();
                    objectiveItemCount += detail.getTotal();
                    objectiveItemErrorCount += wrongQuestion.intValue();
                }
            }
            practiceRecord.setTypeReadingDTOs(details);
            practiceRecord.setObjectiveItemCount(objectiveItemCount);
            practiceRecord.setObjectiveItemErrorCount(objectiveItemErrorCount);
        }
    }

    private boolean showAnswer(Long chapterId) {
        List<Permission> permissions = configService.queryPermission(
                Lists.newArrayList(chapterId), PermissionEnum.PRACTICE_ANSWER_CONTROL);
        if (CollectionUtils.isNotEmpty(permissions) && Objects.equals(permissions.get(0).getStatus(), 1)) {
            return true;
        }
        return false;
    }

}
